package org.mentalsmash.tazio.transactions.objects;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.mentalsmash.documentation.Concurrency;
import org.mentalsmash.tazio.transactions.Transactional;
import org.mentalsmash.tazio.transactions.util.Utility;

/**
 * Container class for properties of a transactional object
 * 
 * @author Andrea Reale
 * @version 0.0.1
 */
@Concurrency(concurrentAccess = false, specification = "Container class. NOT synchornized. "
	+ "Accesses are to be synchronized by clients"

)
class Property implements Serializable {

    private static final long serialVersionUID = 573500948902160993L;
    private final String name;
    private String getterName;
    private Class<?> type;

    private String setterName;
    private final boolean readonly;

    /**
     * Create a new <tt>Property</tt>.
     * <p>
     * If <code>setter</code> is <code>null</code> creates a readonly property
     * </p>
     * 
     * @param name
     *            the name of the property
     * @param getter
     *            the getter method for that property
     * @param setter
     *            the setter method for that property
     */
    public Property(final String name, final Method getter, final Method setter)
	    throws IllegalPropertyException {
	if (name == null || getter == null) {
	    throw new IllegalPropertyTypeException(
		    "Cannot create a property having null name or");
	} else if (getter.equals(setter)) {
	    throw new IllegalPropertyTypeException(
		    "Setter Cannot be equal to Getter");
	}

	this.name = name;
	setGetter(getter);
	setSetter(setter);
	this.readonly = (setter == null);

    }

    /**
     * @param getter
     */
    private void setGetter(final Method getter) throws IllegalPropertyException {
	final Class<?> returnType;
	// check for empty arguments
	if (getter.getParameterTypes().length != 0) {
	    throw new IllegalPropertyException(
		    "A getter method must have no parameters");
	}

	// check for valid return type
	returnType = getter.getReturnType();
	final boolean isPrimitive = Utility.isPrimitive(returnType);
	if (!isPrimitive) {
	    final boolean isTransactionalInterface = (Utility
		    .findTransactionalInterfaces(returnType).length != 0);
	    if (!isTransactionalInterface) {
		throw new IllegalPropertyException(
			"A getter method must return a primitive type or a Transactional Object");
	    }
	}
	this.getterName = getter.getName();
	this.type = getter.getReturnType();

    }

    /**
     * @param setter
     */
    private void setSetter(final Method setter) throws IllegalPropertyException {
	final Class<?> argument;

	if (setter != null) {
	    // check for void return type
	    if (!setter.getReturnType().equals(Void.TYPE)) {
		throw new IllegalPropertyException(
			"A setter method must have a void return type");
	    }

	    // check for single parameter
	    if (!(setter.getParameterTypes().length == 1)) {
		throw new IllegalPropertyException(
			"A setter method must have exactly one parameter");
	    }
	    argument = setter.getParameterTypes()[0];

	    if (!argument.equals(this.type)) {
		throw new IllegalPropertyException(
			"The argument for a setter must be of the same type of the return type of the getter");
	    }

	    this.setterName = setter.getName();
	} else {
	    this.setterName = null;
	}

    }

    /**
     * Retunrs the name of the property
     * 
     * @return the name of the property
     */
    public String getName() {
	return this.name;
    }

    // /**
    // * Returns a reference to the getter method for the property
    // *
    // * @return a reference to the getter method for the property
    // */
    // public Method getter() {
    // return this.getter;
    // }
    //
    // /**
    // * Returns a reference to the setter method for the property, or
    // * <code>null</code> if {@link #isReadonly()} returns true
    // *
    // * @return a reference to the setter method for the property, or
    // * <code>null</code> if {@link #isReadonly()} returns true
    // */
    // public Method setter() {
    // return this.setter;
    // }

    public String getterName() {
	return this.getterName;
    }

    public String setterName() {
	return this.setterName;
    }

    /**
     * Returns <code>true</code> if the property is a readonly property.
     * 
     * @return <code>true</code> if the property is a readonly property.
     */
    public boolean isReadonly() {
	return this.readonly;
    }

    /**
     * Returns the type of this property. Notice that this is either an instance
     * of an interface annotated with {@link Transactional}, or a primitive type
     * 
     * @return the type of this property.
     */
    public Class<?> getPropertyType() {
	return this.type;
    }

}
